﻿//-----------------------------------------------------------------------
// <copyright file="FileRuleSetProvider.cs" company="tuliprules.org">
//     Copyright (c) 2010 tuplelister, tuliprules.org.
// </copyright>
// <summary>This is the FileRuleSetProvider class.</summary>
//-----------------------------------------------------------------------

namespace Tulip.Rules
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Security.Principal;
    using System.Xml.Serialization;

    /// <summary>
    /// Rule set provider for rule sets sored in files.
    /// </summary>
    public class FileRuleSetProvider : IRuleSetProvider
    {
        /// <summary>
        /// An optional Windows identity used for impersonation purposes.
        /// </summary>
        private readonly WindowsIdentity windowsIdentity;

        /// <summary>
        /// The pathname for the file-based store.  NB. The file represents a serialised
        /// RuleSetDictionary, and contains XML.
        /// </summary>
        private readonly string filePath;

        /// <summary>
        /// An internal RuleSet dictionary that represents the contents of the fil;e store.
        /// </summary>
        private RuleSetDictionary fileRuleSets;

        /// <summary>
        /// Initializes a new instance of the FileRuleSetProvider class.
        /// </summary>
        /// <param name="filePath">
        /// The pathname for the file-based store.
        /// </param>
        public FileRuleSetProvider(string filePath)
        {
            this.windowsIdentity = WindowsIdentity.GetCurrent();
            this.filePath = filePath;
            this.LoadFile();
        }

        /// <summary>
        /// Initializes a new instance of the FileRuleSetProvider class.
        /// </summary>
        /// <param name="filePath">
        /// The pathname for the file-based store.
        /// </param>
        /// <param name="windowsIdentity">
        /// An optional Windows identity used for impersonation purposes.
        /// </param>
        public FileRuleSetProvider(string filePath, WindowsIdentity windowsIdentity)
        {
            this.windowsIdentity = windowsIdentity;
            this.filePath = filePath;
            this.LoadFile();
        }

        /// <summary>
        /// Adds a rule set to the provider store.
        /// </summary>
        /// <param name="ruleSet">The rule set to be added to the store.</param>
        public void Add(RuleSet ruleSet)
        {
            if (ruleSet == null)
            {
                return;
            }

            if (this.fileRuleSets.ContainsKey(ruleSet.Name))
            {
                throw new RulesException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        Properties.Resources.ExRuleSetAlreadyExists,
                        ruleSet.Name,
                        ruleSet.MajorVersion,
                        ruleSet.MinorVersion,
                        ruleSet.Build,
                        ruleSet.Revision));
            }

            this.fileRuleSets.Add(ruleSet.Name, ruleSet);
            this.SaveFile();
            return;
        }

        /// <summary>
        /// Adds a keyed collection of rule sets to the provider store.
        /// </summary>
        /// <param name="ruleSets">A keyed collection of rule sets to be added to the store.</param>
        public void Add(RuleSetDictionary ruleSets)
        {
            if (ruleSets.Count == 0)
            {
                return;
            }

            foreach (var ruleSet in ruleSets.Values.Where(ruleSet => ruleSet != null))
            {
                if (this.fileRuleSets.ContainsKey(ruleSet.Name))
                {
                    throw new RulesException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            Properties.Resources.ExRuleSetAlreadyExists,
                            ruleSet.Name,
                            ruleSet.MajorVersion,
                            ruleSet.MinorVersion,
                            ruleSet.Build,
                            ruleSet.Revision));
                }

                this.fileRuleSets.Add(ruleSet.Name, ruleSet);
            }

            this.SaveFile();
            return;
        }

        /// <summary>
        /// Deletes a rule set from the provider store.
        /// </summary>
        /// <param name="ruleSetInfo">Rule set metadata used to select the required rule set.</param>
        public void Delete(RuleSetInfo ruleSetInfo)
        {
            if (ruleSetInfo == default(RuleSetInfo))
            {
                return;
            }

            if (this.fileRuleSets.ContainsKey(ruleSetInfo.Name))
            {
                if (this.TryRemoveRuleSet(this.fileRuleSets[ruleSetInfo.Name], ruleSetInfo))
                {
                    this.SaveFile();
                }
            }
            else
            {
                if (this.fileRuleSets.Values.Where(ruleSet => this.TryRemoveRuleSet(ruleSet, ruleSetInfo)).Count() > 0)
                {
                    this.SaveFile();
                }
            }
        }

        /// <summary>
        /// Deletes all rule sets from the provider store.  The file will be deleted.
        /// </summary>
        public void DeleteAll()
        {
            this.fileRuleSets.Clear();
            this.SaveFile();
        }

        /// <summary>
        /// Returns a rule set from the provider store.
        /// </summary>
        /// <param name="ruleSetInfo">Rule set metadata used to select the required rule set.</param>
        /// <returns>A rule set.</returns>
        public RuleSet GetRuleSet(RuleSetInfo ruleSetInfo)
        {
            if (ruleSetInfo == default(RuleSetInfo))
            {
                throw new ArgumentException(Properties.Resources.ExRuleSetInfoInvalid);
            }

            if (this.fileRuleSets.ContainsKey(ruleSetInfo.Name))
            {
                return this.fileRuleSets[ruleSetInfo.Name];
            }

            foreach (
                var ruleSet in
                this.fileRuleSets.Values.Where(ruleSet => ruleSet.RuleSetInfo.Equals(ruleSetInfo)))
            {
                return ruleSet;
            }

            throw new RulesException(
                string.Format(
                    CultureInfo.CurrentCulture, 
                    Properties.Resources.ExRuleSetNotFound,
                    ruleSetInfo.Name, 
                    ruleSetInfo.MajorVersion, 
                    ruleSetInfo.MinorVersion,
                    ruleSetInfo.Build, 
                    ruleSetInfo.Revision));
        }

        /// <summary>
        /// Saves a rule set dictionary to the provider store.  This will overwrite any
        /// existing rule sets in the file store.
        /// </summary>
        /// <param name="ruleSetDictionary">
        /// The rule set dictionary to be saved to the store.
        /// </param>
        public void Save(RuleSetDictionary ruleSetDictionary)
        {
            if (ruleSetDictionary == null)
            {
                return;
            }

            this.fileRuleSets = ruleSetDictionary;
            this.SaveFile();
        }

        /// <summary>
        /// Loads a rule set dictionary from a given file.
        /// </summary>
        private void LoadFile()
        {
            try
            {
                using (new WindowsImpersonationManager(this.windowsIdentity))
                {
                    var fileInfo = new FileInfo(this.filePath);

                    if (fileInfo.Exists)
                    {
                        using (var fileStream = new FileStream(this.filePath, FileMode.Open, FileAccess.Read))
                        {
                            var serializer = new XmlSerializer(typeof(RuleSetDictionary));
                            this.fileRuleSets = (RuleSetDictionary)serializer.Deserialize(fileStream);
                        }
                    }
                    else
                    {
                        this.fileRuleSets = new RuleSetDictionary();
                    }
                }
            }
            catch (RulesException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new RulesException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.ExFileNotLoaded, this.filePath, this.windowsIdentity.Name, ex.Message), ex);
            }
        }

        /// <summary>
        /// Saves the rule set dictionary maintained by this instance to a designated file.
        /// </summary>
        private void SaveFile()
        {
            if (this.fileRuleSets == null)
            {
                return;
            }

            try
            {
                using (new WindowsImpersonationManager(this.windowsIdentity))
                {
                    var fileInfo = new FileInfo(this.filePath);

                    if (this.fileRuleSets.Count == 0)
                    {
                        if (fileInfo.Exists)
                        {
                            fileInfo.Delete();
                            return;
                        }
                    }

                    using (var fileStream = new FileStream(this.filePath, FileMode.Create, FileAccess.Write))
                    {
                        var serializer = new XmlSerializer(typeof(RuleSetDictionary));
                        serializer.Serialize(fileStream, this.fileRuleSets);
                    }
                }
            }
            catch (RulesException)
            {
                throw;
            }
            catch (Exception ex)
            {
                throw new RulesException(string.Format(CultureInfo.CurrentCulture, Properties.Resources.ExFileNotSaved, this.filePath, this.windowsIdentity.Name, ex.Message), ex);
            }
        }

        /// <summary>
        /// Attempt to remove a rule set from the rule set dictionary.
        /// </summary>
        /// <param name="candidateRuleSet">the candidate rule set to be removed.</param>
        /// <param name="ruleSetInfo">The rule set info of the requested rule set.</param>
        /// <returns>True, if a rule set was found and removed; otherwise false.</returns>
        private bool TryRemoveRuleSet(RuleSet candidateRuleSet, RuleSetInfo ruleSetInfo)
        {
            if (candidateRuleSet == null || ruleSetInfo == default(RuleSetInfo))
            {
                return false;
            }

            if (candidateRuleSet.RuleSetInfo.Equals(ruleSetInfo))
            {
                this.fileRuleSets.Remove(ruleSetInfo.Name);
                return true;
            }

            return false;
        }
    }
}
